/*
Copyright (C) 2022-2024 Inspur Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package client

import (
	"encoding/hex"
	"encoding/json"
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/chain"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/iam"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/client/ledger"
	config2 "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/core/config"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain_sdk"
	iam2 "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/iam"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"

	"did-service/chain/artifact"
	"did-service/chain/model"
	"did-service/chain/utils"
	"did-service/common/constant"
	"did-service/common/logger"
	"did-service/config"
)

var log = logger.GetLogger("chain")

const (
	DefaultContractName = "did-cc"

	FuncSaveDidInfo          = "saveDidInfo"
	FuncGetDidInfo           = "getDidInfo"
	FuncSaveIssuerInfo       = "saveIssuerInfo"
	FuncGetIssuerInfo        = "getIssuerInfo"
	FuncSaveCptInfo          = "saveCptInfo"
	FuncGetCptInfo           = "getCptInfo"
	FuncSaveCredentialInfo   = "saveCredentialInfo"
	FuncGetCredentialInfo    = "getCredentialInfo"
	FuncSavePresentationInfo = "savePresentationInfo"
	FuncGetPresentationInfo  = "getPresentationInfo"
)

type IchainClient struct {
	Config
	sdk    *ichain_sdk.IChainSDK
	chain  *chain.Client
	ledger *ledger.Client
}

type Config struct {
	OrgName        string
	ServiceName    string
	ChainName      string
	ChainLog       string
	Alg            string
	HashAlg        string
	ConnectionFile []byte
	Orgs           []artifact.Org
	Consensers     []artifact.Node
	Nodes          []string
	Cert           []byte
	PrvKey         []byte
}

func NewClient(c Config) *IchainClient {
	return &IchainClient{
		Config: c,
	}
}

func (client *IchainClient) Initialize() error {
	cc := artifact.NewConnectConfig(client.ServiceName, client.ChainName, client.Alg, client.HashAlg, client.ChainLog)
	connectBytes := cc.SetOrgs(client.Orgs).SetConsensers(client.Consensers).Build()
	log.Debugf("connect artifact: %s", string(connectBytes))
	client.ConnectionFile = connectBytes
	sdk, err := ichain_sdk.New(config2.FromRaw(connectBytes, "yaml"))
	if err != nil {
		log.Errorf("initialize ichain client, new sdk error: %v", err)
		return err
	}
	client.sdk = sdk

	iamInstance, err := iam.New(sdk.Context(), iam.WithOrg(client.OrgName))
	if err != nil {
		log.Errorf("initialize ichain client, new iam client error: %v", err)
		return err
	}
	identity, err := iamInstance.CreateSigningIdentity(iam2.WithCert(client.Cert), iam2.WithPrivateKey(client.PrvKey))
	if err != nil {
		log.Errorf("initialize ichain client, create identity error: %v", err)
		return err
	}

	chainInstance, err := chain.New(sdk.ChainContext(client.ChainName, ichain_sdk.WithIdentity(identity)))
	if err != nil {
		log.Errorf("initialize ichain client, new chain client error: %v", err)
		return err
	}
	client.chain = chainInstance

	ledgerInstance, err := ledger.New(sdk.ChainContext(client.ChainName, ichain_sdk.WithIdentity(identity)))
	if err != nil {
		log.Errorf("initialize ichain client, new ledger client error: %v", err)
		return err
	}
	client.ledger = ledgerInstance
	return nil
}

func (client *IchainClient) SaveDidInfo(didInfo *model.DidInfo) (txId, payload string) {
	bytes, _ := json.Marshal(didInfo)
	response, err := client.executeContract(config.Get("did.chain.contract-name", DefaultContractName), FuncSaveDidInfo, didInfo.Did, string(bytes))
	if err != nil {
		log.Errorf("save did info error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

func (client *IchainClient) GetDidInfo(did string) string {
	response, err := client.queryContract(config.Get("did.chain.contract-name", DefaultContractName), FuncGetDidInfo, did)
	if err != nil {
		log.Errorf("get did info error: %v", err)
		return ""
	}
	return string(response.Payload)
}

func (client *IchainClient) SaveIssuerInfo(issuerInfo *model.IssuerInfo) (txId, payload string) {
	bytes, _ := json.Marshal(issuerInfo)
	response, err := client.executeContract(config.Get("did.chain.contract-name", DefaultContractName), FuncSaveIssuerInfo, issuerInfo.Id, string(bytes))
	if err != nil {
		log.Errorf("save issuer info error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

func (client *IchainClient) GetIssuerInfo(issuerId string) string {
	response, err := client.queryContract(config.Get("did.chain.contract-name", DefaultContractName), FuncGetIssuerInfo, issuerId)
	if err != nil {
		log.Errorf("get issuer info error: %v", err)
		return ""
	}
	return string(response.Payload)
}

func (client *IchainClient) SaveCptInfo(cptInfo *model.CptInfo) (txId, payload string) {
	bytes, _ := json.Marshal(cptInfo)
	response, err := client.executeContract(config.Get("did.chain.contract-name", DefaultContractName), FuncSaveCptInfo, cptInfo.Id, string(bytes))
	if err != nil {
		log.Errorf("save cpt info error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

func (client *IchainClient) GetCptInfo(cptId string) string {
	response, err := client.queryContract(config.Get("did.chain.contract-name", DefaultContractName), FuncGetCptInfo, cptId)
	if err != nil {
		log.Errorf("get cpt info error: %v", err)
		return ""
	}
	return string(response.Payload)
}

func (client *IchainClient) SaveCredentialInfo(info *model.CredentialInfo) (txId, payload string) {
	bytes, _ := json.Marshal(info)
	response, err := client.executeContract(config.Get("did.chain.contract-name", DefaultContractName), FuncSaveCredentialInfo, info.Id, string(bytes))
	if err != nil {
		log.Errorf("save credential info error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

func (client *IchainClient) GetCredentialInfo(id string) string {
	response, err := client.queryContract(config.Get("did.chain.contract-name", DefaultContractName), FuncGetCredentialInfo, id)
	if err != nil {
		log.Errorf("get credential info error: %v", err)
		return ""
	}
	return string(response.Payload)
}

func (client *IchainClient) SavePresentationInfo(info *model.PresentationInfo) (txId, payload string) {
	bytes, _ := json.Marshal(info)
	response, err := client.executeContract(config.Get("did.chain.contract-name", DefaultContractName), FuncSavePresentationInfo, info.Id, string(bytes))
	if err != nil {
		log.Errorf("save presentation info error: %v", err)
		txId = ""
		payload = ""
		return
	}
	txId = string(response.TransactionID)
	payload = string(response.Payload)
	return
}

func (client *IchainClient) GetPresentationInfo(id string) string {
	response, err := client.queryContract(config.Get("did.chain.contract-name", DefaultContractName), FuncGetPresentationInfo, id)
	if err != nil {
		log.Errorf("get presentation info error: %v", err)
		return ""
	}
	return string(response.Payload)
}

func (client *IchainClient) queryContract(contractName, fcn string, params ...string) (chain.Response, error) {
	args := make([][]byte, len(params))
	for i, param := range params {
		args[i] = []byte(param)
	}
	req := chain.Request{
		ContractID: contractName,
		Fcn:        fcn,
		Args:       args,
	}

	response, err := client.chain.Query(req, chain.WithTargetEndpoints(client.Nodes...))
	return response, err
}

func (client *IchainClient) executeContract(contractName, fcn string, params ...string) (chain.Response, error) {
	args := make([][]byte, len(params))
	for i, param := range params {
		args[i] = []byte(param)
	}
	req := chain.Request{
		ContractID: contractName,
		Fcn:        fcn,
		Args:       args,
	}
	response, err := client.chain.Execute(req, chain.WithTargetEndpoints(client.Nodes...))
	return response, err
}

func (client *IchainClient) GetTransactionByTxId(txId string) (txInfo *model.TransactionInfo, err error) {
	txInfo = &model.TransactionInfo{}

	block, err := client.ledger.QueryBlockByTxID(ichain.TransactionID(txId))
	if err != nil {
		return
	}

	txInfo.BlockNumber = block.Header.Number
	txInfo.BlockHash = hex.EncodeToString(block.Header.DataHash)

	pt, err := client.ledger.QueryTransaction(ichain.TransactionID(txId))
	if err != nil {
		return
	}
	envelope := pt.GetTransactionEnvelope()
	payload, err := utils.GetPayload(envelope)
	if err != nil {
		return
	}
	chainHeader, err := utils.UnmarshalChainHeader(payload.Header.ChainHeader)
	if err != nil {
		return
	}
	txInfo.TxId = chainHeader.GetTxId()
	txInfo.TxTime = time.Unix(chainHeader.Timestamp.Seconds, 0).Format(constant.TimeLayout)
	signatureHeader, err := utils.UnmarshalSignatureHeader(payload.Header.SignatureHeader)
	if err != nil {
		return
	}
	txInfo.Nonce = hex.EncodeToString(signatureHeader.GetNonce())

	transaction, err := utils.UnmarshalTransaction(payload.Data)
	if err != nil {
		return
	}

	actionPayload, err := utils.UnmarshalContractActionPayload(transaction.Actions[0].Payload)
	if err != nil {
		return
	}

	proposalPayload, err := utils.UnmarshalContractProposalPayload(actionPayload.ContractProposalPayload)
	if err != nil {
		return
	}

	spec, err := utils.UnmarshalContractInvocationSpec(proposalPayload.Input)
	txInfo.ContractId = spec.ContractSpec.ContractId.Name
	txInfo.ContractVersion = spec.ContractSpec.ContractId.Version
	var args []string
	for _, arg := range spec.ContractSpec.Input.Args {
		args = append(args, string(arg))
	}
	txInfo.Args = args
	return
}
